FISHER_EXACT
Overview
The FISHER_EXACT function performs Fisher’s exact test on a 2×2 contingency table to assess whether there is a statistically significant association between two categorical variables. Unlike the chi-squared test, which relies on asymptotic approximations, Fisher’s exact test calculates the exact probability of observing the given table (or more extreme configurations) under the null hypothesis that the two variables are independent.
The test was developed by Ronald Fisher in 1935 and famously demonstrated through the “lady tasting tea” experiment. It is particularly valuable when sample sizes are small or when expected cell frequencies are too low for the chi-squared approximation to be reliable (typically when any expected value is below 5). For more background, see the Wikipedia article on Fisher’s exact test.
This implementation uses SciPy’s fisher_exact function. Given a 2×2 table with cells [[a, b], [c, d]], the test conditions on the marginal totals and evaluates probabilities using the hypergeometric distribution. The probability of obtaining the observed table is:
p = \frac{(a+b)!\,(c+d)!\,(a+c)!\,(b+d)!}{a!\,b!\,c!\,d!\,n!}
where n = a + b + c + d is the total sample size.
The function returns two values: the odds ratio and the p-value. The odds ratio is the unconditional maximum likelihood estimate, calculated as (a \times d) / (b \times c). The p-value represents the probability of observing a table as extreme as (or more extreme than) the observed table under the null hypothesis. The fisher_alternative parameter controls whether the test is two-sided (default), or one-sided testing whether the true odds ratio is less than or greater than one.
Fisher’s exact test is conservative when marginals are not truly fixed by design. For situations requiring more statistical power, consider Barnard’s exact test or Boschloo’s test, which are uniformly more powerful alternatives available in SciPy.
This example function is provided as-is without any representation of accuracy.
Excel Usage
=FISHER_EXACT(table, fisher_alternative)
table(list[list], required): A 2x2 contingency table with non-negative integer entries.fisher_alternative(str, optional, default: “two-sided”): Defines the alternative hypothesis for the test.
Returns (list[list]): 2D list [[odds_ratio, p_value]], or error message string.
Examples
Example 1: 2x2 table with two-sided test
Inputs:
| table | fisher_alternative | |
|---|---|---|
| 8 | 2 | two-sided |
| 1 | 5 |
Excel formula:
=FISHER_EXACT({8,2;1,5}, "two-sided")
Expected output:
| Result | |
|---|---|
| 20 | 0.035 |
Example 2: 2x2 table with greater alternative
Inputs:
| table | fisher_alternative | |
|---|---|---|
| 8 | 2 | greater |
| 1 | 5 |
Excel formula:
=FISHER_EXACT({8,2;1,5}, "greater")
Expected output:
| Result | |
|---|---|
| 20 | 0.0245 |
Example 3: 2x2 table with less alternative
Inputs:
| table | fisher_alternative | |
|---|---|---|
| 8 | 2 | less |
| 1 | 5 |
Excel formula:
=FISHER_EXACT({8,2;1,5}, "less")
Expected output:
| Result | |
|---|---|
| 20 | 0.9991 |
Example 4: 2x2 table with default alternative
Inputs:
| table | |
|---|---|
| 10 | 5 |
| 3 | 8 |
Excel formula:
=FISHER_EXACT({10,5;3,8})
Expected output:
| Result | |
|---|---|
| 5.3333 | 0.1107 |
Python Code
from scipy.stats import fisher_exact as scipy_fisher_exact
import numpy as np
def fisher_exact(table, fisher_alternative='two-sided'):
"""
Perform Fisher's exact test on a 2x2 contingency table.
See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.fisher_exact.html
This example function is provided as-is without any representation of accuracy.
Args:
table (list[list]): A 2x2 contingency table with non-negative integer entries.
fisher_alternative (str, optional): Defines the alternative hypothesis for the test. Valid options: Two-sided, Less, Greater. Default is 'two-sided'.
Returns:
list[list]: 2D list [[odds_ratio, p_value]], or error message string.
"""
def to2d(x):
return [[x]] if not isinstance(x, list) else x
table = to2d(table)
# Validate table structure
if not isinstance(table, list) or len(table) < 2:
return "Invalid input: table must be a 2D list with at least 2 rows."
if not all(isinstance(row, list) and len(row) >= 2 for row in table):
return "Invalid input: table must be a 2D list with at least 2 columns per row."
try:
arr = np.array(table, dtype=float)
if arr.ndim != 2 or arr.shape[0] != 2 or arr.shape[1] != 2:
return "Invalid input: table must be a 2x2 contingency table."
# Check for non-negative integers (allow floats that are whole numbers)
if np.any(arr < 0):
return "Invalid input: table must contain non-negative values."
if not np.allclose(arr, arr.astype(int)):
return "Invalid input: table must contain integer values."
arr = arr.astype(int)
except (ValueError, TypeError):
return "Invalid input: table must be convertible to a 2D array of non-negative integers."
# Validate alternative
valid_alternatives = ['two-sided', 'less', 'greater']
if fisher_alternative not in valid_alternatives:
return f"Invalid input: fisher_alternative must be one of {valid_alternatives}."
try:
res = scipy_fisher_exact(arr, alternative=fisher_alternative)
stat, pval = res.statistic, res.pvalue
except Exception as e:
return f"scipy.stats.fisher_exact error: {e}"
# Check for invalid outputs
if np.isnan(stat) or np.isinf(stat) or np.isnan(pval) or np.isinf(pval):
return "Invalid output: statistic or p-value is NaN or infinite."
return [[float(stat), float(pval)]]